package com.system.core.security;

import java.io.StringWriter;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;

import com.system.core.security.enums.AlgorithmEnum;
import com.system.core.security.enums.KeyPairGeneratorAlgorithmEnum;
import com.system.core.security.enums.MessageDigestAlgorithmEnum;
import com.system.core.security.enums.SignatureAlgorithmEnum;

/**
 * 
 *
 * <pre class="code">
 * </pre>
 * @author jlcon
 */
public class SecurityTool {
	
	private static final String securityKey = "I6&ur5I%1WGGj3RlnYnuj#7dV26lM8^J";
	
	static {
		Security.addProvider(new BouncyCastleProvider());
	}

	/**
	 * 
	 * <font size="3" color="red">decryptWithKey</font><br>
	 * <ol>
	 * <li></li>
	 * </ol>
	 * @param encryptStr 加密字符
	 * @param key 解密密钥
	 * @return 解密字符
	 * @throws Exception <div style="color:blue;font-size:12px;">异常</div>
	 */
	public static String decryptWithKey(String encryptStr,Key key) throws Exception{
		
		Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, key);
        //当长度过长的时候，需要分割后解密 128个字节
        String outStr = new String(getMaxResultDecrypt(encryptStr,  cipher));

		return outStr;
	}
	
	/**
	 * 
	 * <font size="3" color="red">digest</font><br>
	 * <ol>
	 * <li></li>
	 * </ol>
	 * @param src
	 * @param algorithmEnum
	 * @return
	 * @throws Exception <div style="color:blue;font-size:12px;">异常</div>
	 */
	public static String digest(String src,MessageDigestAlgorithmEnum messageDigestAlgorithmEnum) throws Exception{
		MessageDigest md = MessageDigest.getInstance(messageDigestAlgorithmEnum.getCode());
		md.update(src.getBytes());
		return Hex.encodeHexString(md.digest());
	}
	public static String digest(byte[] src,MessageDigestAlgorithmEnum messageDigestAlgorithmEnum) throws Exception{
		MessageDigest md = MessageDigest.getInstance(messageDigestAlgorithmEnum.getCode());
		md.update(src);
		return Hex.encodeHexString(md.digest());
	}
	
	/**
	 * 
	 * <font size="3" color="red">encryptWithKey</font><br>
	 * <ol>
	 * <li></li>
	 * </ol>
	 * @param src 加密原数据
	 * @param key 加密密钥
	 * @return 加密字符
	 * @throws Exception <div style="color:blue;font-size:12px;">异常</div>
	 */
	public static String encryptWithKey(String src,Key key) throws Exception{
		byte[] work = src.getBytes();
		int offSet = 0;
		byte[] resultBytes = {};
		byte[] cache = {};
		int MAX_ENCRYPT_BLOCK = 117;
		int inputLength = work.length;
		Cipher cipher = Cipher.getInstance(key.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, key);
		
		while (inputLength - offSet > 0) {
			if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
				cache = cipher.doFinal(work, offSet, MAX_ENCRYPT_BLOCK);
				offSet += MAX_ENCRYPT_BLOCK;
			} else {
				cache = cipher.doFinal(work, offSet, inputLength - offSet);
				offSet = inputLength;
			}
			resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
			System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
		}
		return Base64.encodeBase64String(resultBytes);
	}
	
	private static String format2PemString(String type, byte[] privateKeyPKCS1) throws Exception {
        PemObject pemObject = new PemObject(type, privateKeyPKCS1);
        StringWriter stringWriter = new StringWriter();
        PemWriter pemWriter = new PemWriter(stringWriter);
        pemWriter.writeObject(pemObject);
        pemWriter.close();
        String pemString = stringWriter.toString();
        return pemString;
    }
	
	/**
	 * 私钥PKCS8转换为PKCS1
	 */
	public static String formatPkcs8ToPkcs1(String rawKey) throws Exception {
        String result = null;
        if (StringUtils.isNotBlank(rawKey)){
            //将BASE64编码的私钥字符串进行解码
            byte[] encodeByte = Base64.decodeBase64(rawKey);
            PrivateKeyInfo pki = PrivateKeyInfo.getInstance(encodeByte);
            RSAPrivateKeyStructure pkcs1Key = RSAPrivateKeyStructure.getInstance(pki.getPrivateKey());
            byte[] pkcs1Bytes = pkcs1Key.getEncoded();
            String type = "RSA PRIVATE KEY";
            result = format2PemString(type, pkcs1Bytes);
        }
        return result;
    }
	
	/**
	 * 
	 * <div style="color:red;font-size:18px;">generateKey</div><br>
	 * <ol>
	 * <li></li>
	 * </ol>
	 * @param algorithmEnum
	 * @param keySize
	 * @return <div style="color:blue;font-size:12px;"></font>
	 */
	public static KeyPair generateKey(KeyPairGeneratorAlgorithmEnum keyPairGeneratorAlgorithmEnum,int keySize){
		try {
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyPairGeneratorAlgorithmEnum.getCode());
			keyPairGenerator.initialize(keySize);
			KeyPair keyPair = keyPairGenerator.generateKeyPair();
			return keyPair;
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	
	/**
	 * 私钥PKCS1转换为PKCS8
	 */
//	public static String formatPkcs1ToPkcs8(String rawKey) throws Exception {
//        String result = null;
//        if (StringUtils.isNotBlank(rawKey)){
//            //将BASE64编码的私钥字符串进行解码
//            byte[] encodeByte = Base64.decodeBase64(rawKey);
//            AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag);
//            ASN1Object asn1Object = ASN1ObjectIdentifier.fromByteArray(encodeByte);
//            PrivateKeyInfo privKeyInfo = new PrivateKeyInfo(algorithmIdentifier, asn1Object);
//            byte[] pkcs8Bytes = privKeyInfo.getEncoded();
//            return Base64.encodeBase64String(pkcs8Bytes);
//        }
//        return result;
//    }
	
	private static byte[] getMaxResultDecrypt(String str, Cipher cipher) throws Exception {
        byte[] inputArray = Base64.decodeBase64(str.getBytes("UTF-8"));
        int inputLength = inputArray.length;
        // 最大解密字节数，超出最大字节数需要分组加密
        int MAX_ENCRYPT_BLOCK = 128;
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while (inputLength - offSet > 0) {
            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }
	
	private static byte[] getMaxResultEncrypt(String str, Cipher cipher) throws IllegalBlockSizeException, BadPaddingException {
        byte[] inputArray = str.getBytes();
        int inputLength = inputArray.length;
        // 最大加密字节数，超出最大字节数需要分组加密
        int MAX_ENCRYPT_BLOCK = 117;
        // 标识
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while (inputLength - offSet > 0) {
            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }
	
	/**
	 * 
	 * <font size="3" color="red">从Base64文件中获取私钥</font><br>
	 * <ol>
	 * <li>内含获取公钥方法</li>
	 * </ol>
	 * @param pkey base64编码的私钥字符串
	 * @return
	 * @throws Exception <div style="color:blue;font-size:12px;">异常</div>
	 */
	public static PrivateKey getPrivateKey(String keyBase64,AlgorithmEnum algorithmEnum) throws Exception{
		byte[] keyBytes = Base64.decodeBase64(keyBase64);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(algorithmEnum.getCode());
		return keyFactory.generatePrivate(pkcs8KeySpec);
	}
	
	/**
	 * 
	 * <font size="3" color="red">getPublicKey</font><br>
	 * <ol>
	 * <li></li>
	 * </ol>
	 * @param keyBase64
	 * @param algorithmEnum
	 * @return
	 * @throws Exception <div style="color:blue;font-size:12px;">异常</div>
	 */
	public static PublicKey getPublicKey(String keyBase64,AlgorithmEnum algorithmEnum) throws Exception{
		byte[] keyBytes = Base64.decodeBase64(keyBase64);
//		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(algorithmEnum.getCode());
//		return keyFactory.generatePublic(pkcs8KeySpec);
		return keyFactory.generatePublic(keySpec);
	}
	
	/**
	 * 列举所有密钥对生成算法
	 */
	public static List<String> listAllKeyPairGenerator(){
		List<String> keyPairGenerator = new ArrayList<>();
		for (String s : Security.getAlgorithms("KeyPairGenerator")) {
			keyPairGenerator.add(s);
			System.out.println(s);
		}
		return keyPairGenerator;
	}

	/**
	 * 列举所有摘要算法
	 */
	public static List<String> listAllMessageDigest(){
		List<String> messageDigest = new ArrayList<>();
		for (String s : Security.getAlgorithms("MessageDigest")) {
			messageDigest.add(s);
		}
		return messageDigest;
	}
	
	/**
	 * 列举所有签名算法
	 */
	public static List<String> listAllSignature(){
		List<String> signature = new ArrayList<>();
		for (String s : Security.getAlgorithms("Signature")) {
			signature.add(s);
		}
		return signature;
	}
	
	/**
	 * 打印所有算法
	 */
	public static void printAllAlgorithm() {
		for (Provider provider : Security.getProviders()) {
			System.out.println("--------------------------------------------");
			provider.getServices().forEach(item->{
				System.out.println(item.getProvider().getName()+" : "+item.getType()+" - "+item.getAlgorithm());
			});
		}
	}
	
	public static String signWithMyDigestPrivateKey(String src,SignatureAlgorithmEnum signatureAlgorithmEnum,PrivateKey privateKey) throws Exception{
		Signature signature = Signature.getInstance(signatureAlgorithmEnum.getCode());
		signature.initSign(privateKey);
		signature.update(DigestUtils.md5(src+securityKey));
		return Base64.encodeBase64String(signature.sign());
	}
	

	public static String signWithPrivateKey(String src,SignatureAlgorithmEnum signatureAlgorithmEnum,PrivateKey privateKey) throws Exception{
		Signature signature = Signature.getInstance(signatureAlgorithmEnum.getCode());
		signature.initSign(privateKey);
		signature.update(src.getBytes());
		return Base64.encodeBase64String(signature.sign());
	}
	
	public static boolean verifyWithMyDigestPublicKey(String src,String sign,SignatureAlgorithmEnum algorithmEnum,PublicKey publicKey) throws Exception{
		Signature signature = Signature.getInstance(algorithmEnum.getCode());
		signature.initVerify(publicKey);
		signature.update(DigestUtils.md5(src+securityKey));
		return signature.verify(Base64.decodeBase64(sign));
	}
	
	public static boolean verifyWithPublicKey(String src,String sign,SignatureAlgorithmEnum algorithmEnum,PublicKey publicKey) throws Exception{
		Signature signature = Signature.getInstance(algorithmEnum.getCode());
		signature.initVerify(publicKey);
		signature.update(src.getBytes());
		return signature.verify(Base64.decodeBase64(sign));
	}
}
